package model;


import comunication.PlayerCommunicator;
import comunication.SocketServer;


public class Game
{
	private SocketServer server;
	private Player[] players;
	private PlayerCommunicator[] playersComunicators;
	private int plNr;
	private Deck deck ;
	private int startCoins;
	private int whoDealer;
	private int whoseTurn;
	private int playerWithSmallBlind;
	private int stake = 0; // aktualna stawka
	private int round = 1;
	private int smallBlind;
	private int bigBlind;
	private int maxBet;
	private int moveCounter = 1; // liczba wykonanych ruchów w rundzie
	private int playersExchangedCardsCounter  = 0;
	private int playersFoldedCounter = 0;
	private int playersInGameCounter;
	private int playersBeingInGameInActualRound;
	private int playersClosedResults = 0;
	private boolean isBet = false;
	private String possibleMoves = "";
	int sitingPlayersCounter = 0  ; // aktualna liczba siedzacych graczy
	

	public Game(int playersNr, int startCoins, int port, String name, int smallBlind, int bigBlind )
	{
		this.plNr =playersNr;
		this.players = new Player[playersNr+1];
		this.playersComunicators = new PlayerCommunicator[playersNr+1];
		this.startCoins = startCoins;
		this.server = new SocketServer(port, playersNr, this);
		this.smallBlind = smallBlind;
		this.bigBlind = bigBlind;
		server.start();
		this.deck = new Deck();
		this.playersInGameCounter = playersNr;
		this.playersBeingInGameInActualRound = playersNr;
	}
	public void sitPlayer(PlayerCommunicator plCom, String name){
		sitingPlayersCounter++;
		playersComunicators[sitingPlayersCounter] = plCom;
		players[sitingPlayersCounter] = new Player(name, sitingPlayersCounter,startCoins,deck);
		playersComunicators[sitingPlayersCounter].start();
		playersComunicators[sitingPlayersCounter].displayCoins(Integer.toString(startCoins), name);
	}
	public void startGame(){

		whoDealer = Croupier.drawDealer(plNr);
		System.out.println("whoDealer:"+whoDealer);
		try
		{
			setPlayersWithBlinds();
		} catch (GameException e){}
		whoseTurn = Croupier.whichPlayerNext(whoseTurn, plNr);
		playersBeingInGameInActualRound = playersInGameCounter;
		for(int i=1; i<playersComunicators.length; i++)
			playersComunicators[i].displayCards(players[i].getStringHand());
		enableButtons(whoseTurn, isBet, false);
	}
	public void setPlayersWithBlinds() throws GameException{
		playerWithSmallBlind = Croupier.whichPlayerNext2(whoDealer, players,false);
		while(players[playerWithSmallBlind].getPlayersCoins()<smallBlind){
			updateFold(playerWithSmallBlind,true);
			playerWithSmallBlind = Croupier.whichPlayerNext2(whoDealer, players,false);
		}
		playerWithSmallBlind = Croupier.whichPlayerNext2(whoDealer, players,false);
		players[playerWithSmallBlind].setBet(smallBlind);
		displaySmallBlind(players[playerWithSmallBlind].getName(),smallBlind);
		updateCoins(players[playerWithSmallBlind],0-smallBlind);
		int playerWithBigBlind = Croupier.whichPlayerNext2(playerWithSmallBlind, players, false);
		while(players[playerWithBigBlind].getPlayersCoins()<bigBlind){
			updateFold(playerWithBigBlind,true);
			playerWithBigBlind = Croupier.whichPlayerNext2(playerWithSmallBlind, players,false);
		}
		players[playerWithBigBlind].setBet(bigBlind);
		maxBet=bigBlind;
		whoseTurn = playerWithBigBlind;
		displayBigBlind(players[playerWithBigBlind].getName(),bigBlind);
		updateCoins(players[playerWithBigBlind],0-bigBlind);
		updateStake(smallBlind+bigBlind);
	}

	public int getPlayersNumber(){
		return (players.length-1);
	}
	/*
	 * Method returns formated String, which is create in this way:
	 * <player1name><player2name>...
	 * and set name of newPlayer in previous players frames
	 */
	public String introduceNewPlayer(String name){
		String names = "";
		for(int j=sitingPlayersCounter-1; j>0; j--){
			names = names + players[j].getName();
			playersComunicators[sitingPlayersCounter-j].addNewPlayer(name);
		}
		return names;
	}
	public void displayMessage(String message){
		for(int j=1; j<=sitingPlayersCounter;j++){
			playersComunicators[j].displayMessage(message);
		}
	}
	public void displayCoins(int amount, String name){
		String am = Integer.toString(amount);
		for(int i=1; i<players.length; i++){
			playersComunicators[i].displayCoins(am, name);
		}
	}
    private String getWinners() {
        String winners = "";
        int[][] Hands = new int[players.length][];

        for(int i = 1; i < players.length; i++) {
            Hands[i] = Hand.getReducedHand(players[i].getPlayersHand().getCards());
        }
        int[] bestHand = {-1,-1,-1,-1};
        for(int i = 1; i < players.length; i++) {
            if(players[i].getStatus() > Player.PLAYER_OUT_OF_GAME) {
                if(bestHand[0] != -1)
                    bestHand = Hand.giveBetterHand(bestHand, Hands[i]);
                else
                    bestHand = Hands[i];
            }
        }
        for(int i = 1; i < players.length; i++) {
            if(players[i].getStatus() <= Player.PLAYER_OUT_OF_GAME){
                winners += "L";
            }
            else {
                if (!(bestHand.equals(Hands[i]))) {
                    if (!(Hand.giveBetterHand(Hands[i], bestHand).equals(Hand.giveBetterHand(bestHand, Hands[i])))) {
                        winners += "W";
                    } else
                        winners += "L";
                } else
                    winners += "W";
            }
        }
        return winners;

    }
    public void displayWinner() {
        String message = "";
        for(int i = 1; i < players.length; i++) {
            if(players[i].getStatus() <= Player.PLAYER_OUT_OF_GAME) {
                message += players[i].getName() + "FOLDED**"; //must be 8 characters
            }
            else {
                message += players[i].getName() + players[i].getPlayersHand().getString();
            }
        }
        message += getWinners();
        for(int i=1; i<players.length; i++){
            playersComunicators[i].displayWinner(message);
        }
        System.out.println("displayeWinner:" + message);
    }
    public void givePlayerTheirPrize() {
        String winnersString = getWinners();
        int numberOfWinners = 0;
        for(int i = 0; i < winnersString.length(); i++) {
            if(Character.toString(winnersString.charAt(i)).equals("W")) {
                numberOfWinners++;
            }
        }
        if (numberOfWinners == 1) { //Only one winner. Bank doesn't take a prize
            for(int i = 0; i < winnersString.length(); i++) {
                if(Character.toString(winnersString.charAt(i)).equals("W")) {
                    if(players[i+1].getStatus() != Player.PLAYER_PLAYED_ALLIN) { //regular winner (NOT ALL IN)
                        updateCoins(players[i+1], stake);
                    }
                    else { //player[i] played ALL IN
                        int prize = 0;
                        int allInBet = players[i+1].getBet();
                        for (int j = 1; j < players.length; j++) {
                            if(allInBet <= players[j].getBet())
                                prize += allInBet;
                            else
                                prize += players[j].getBet();
                        }
                        updateCoins(players[i+1], prize);
                    }
                }
            }
        }

    }
	public void updateCoins(Player player, int change){
		int newCoins = player.getPlayersCoins() + change;
		player.setPlayersCoins(newCoins);
		String name = player.getName();
		String s = Integer.toString(newCoins);
		for(int i=1; i<players.length; i++){
			playersComunicators[i].displayCoins(s, name);
		}
		System.out.println("updateCoins" + s + name);
	}
	public void updateBet(int playerNr ,String bet) throws GameException{
		try {
			isBet = true;
			int temp = Integer.parseInt(bet); 
			if (temp<=0 && temp>players[playerNr].getPlayersCoins()) throw new GameException("You send incorrect bet");
			int sum = temp + players[playerNr].getBet();
			if (sum > maxBet) maxBet = sum;
			String s = Integer.toString(sum);
			players[playerNr].setBet(sum);
			updateCoins(players[playerNr],0-temp);
			updateStake(temp);
			for(int i=1; i<players.length; i++){
					playersComunicators[i].displayBet(s, players[playerNr].getName());
			}
			nextPlayerMove();
		}
		catch(NumberFormatException e) {
			throw new GameException("You didn't send Integer");
		}
		
	}
	public void updateCall(int playerNr){
		isBet = true;
		String s = Integer.toString(maxBet);
		int change = maxBet-players[playerNr].getBet();
		players[playerNr].setBet(maxBet);
		updateCoins(players[playerNr],0-change);
		updateStake(change);
		for(int i=1; i<players.length; i++){
			playersComunicators[i].displayBet(s, players[playerNr].getName());
		}
		nextPlayerMove();
	}
	/*public void displayPlayersHands() {
        String[] playerHands = new String[6];

    }*/
	public void displayBigBlind(String name, int bBlind){
		String bB = Integer.toString(bBlind);
		for(int i=1; i<players.length; i++){
				playersComunicators[i].displayBigBlind(name, bB);
		}
	}
	public void displaySmallBlind(String name, int sBlind){
		String sB = Integer.toString(sBlind);
		for(int i=1; i<players.length; i++){
				playersComunicators[i].displaySmallBlind(name, sB);
		}
	}
	public void displayNotes(String name,int status){
		if(status==Player.PLAYER_PLAYED_ALLIN){
			for(int i=1; i<players.length; i++){
				playersComunicators[i].displayAllin(name);
			}
		}
		else{
			for(int i=1; i<players.length; i++){
				playersComunicators[i].displayFold(name);
			}
		}
	}
	public void updateStake(int betChange){
		stake = stake + betChange;
		for(int i=1; i<players.length; i++){
			playersComunicators[i].displayStake(Integer.toString(stake));
		}
	}
	public void nextPlayerMove(){
		System.out.println("playersFoldedCounter="+playersFoldedCounter + "\nplayersInActualGame="+playersBeingInGameInActualRound+
				"\nplayersExchangeCardsCounter=" +playersExchangedCardsCounter + "\nplayersInGameCounter=" + playersInGameCounter );
		if( playersFoldedCounter<(plNr-1) ){ 
			if(playersBeingInGameInActualRound>1){ // chociaż dwóch graczy nie dalo fold ani all-in
				whoseTurn = Croupier.whichPlayerNext(whoseTurn, plNr);
		        //gdy wszyscy wymienili karty
		        if(playersExchangedCardsCounter==(plNr-playersFoldedCounter) ){
		        	System.out.println("exch" + playersExchangedCardsCounter +"    ,folded"+playersFoldedCounter);
		                updateRound();
		        }
		        //runda pierwsza i nikt przed osobą z dużą ciemną nie przebił duzej ciemnej
		        if(moveCounter==plNr-1 && round==1 && Croupier.havePlayersTheSameBets(players)){
		        	enableButtons(whoseTurn, isBet,true); //dodatkowo może dać check
		        	moveCounter++;
		        }//POCZATEK WYMIANY wszyscy gracze wykonali mogli wykonać ruch i gracze w grze mają te same zaklady
		        else if (moveCounter>=playersBeingInGameInActualRound && Croupier.havePlayersTheSameBets(players)){
		        	if(round<4){
		        		if(playersExchangedCardsCounter ==0) playersBeingInGameInActualRound = playersInGameCounter;
		        		while(players[whoseTurn].getStatus()<=Player.PLAYER_OUT_OF_GAME){
		        			whoseTurn = Croupier.whichPlayerNext(whoseTurn, plNr);
		        		}
		        		enableCardsExchange(whoseTurn);
		        		playersExchangedCardsCounter++;
		        	}
		        	else{ //KONIEC GRY po 4 rundach- przynajmniej dwóch graczy nie dało fold ani all-in
                        displayWinner();
                        givePlayerTheirPrize();
                        prepareNewDeal();
		        		System.out.println("Koniec pierwszego rozdania");      
		        	}
		        }
		        else{ //Kontynuacja ruchów- rózne zakłady lub nie wszyscy mogli wykonać ruch
		        	while(players[whoseTurn].getStatus()!=Player.PLAYER_IN_GAME){
		        		whoseTurn = Croupier.whichPlayerNext(whoseTurn, plNr);
		            }
		            enableButtons(whoseTurn, isBet,false);
		            moveCounter++;
		        }
			}//jest tylko jeden gracz, który nie dał fold ani all-in, i chociaż jeden z all-in
			else{
				System.out.println("jest tylko jeden gracz, który nie dał fold ani all-in, i chociaż jeden z all-in");
				whoseTurn = Croupier.whichPlayerNext(whoseTurn, plNr);
				//wszyscy wymienili karty
				if(playersExchangedCardsCounter==(plNr-playersFoldedCounter) ){
	                updateRound();
				}
				//trwa wymiana kart
				if(round<4){
					System.out.println("-----Trwa wymiana kart");
		       		while(players[whoseTurn].getStatus()<=Player.PLAYER_OUT_OF_GAME)
		       			whoseTurn = Croupier.whichPlayerNext(whoseTurn, plNr);
		       		enableCardsExchange(whoseTurn);
		       		playersExchangedCardsCounter++;
	        	}
	        	else{//KONIEC GRY
                    displayWinner();
                    givePlayerTheirPrize();
                    prepareNewDeal();
		      		System.out.println("Koniec pierwszego rozdania");     
		       	}
			}
		}
		else{//Koniec GRY został tylko jeden gracz w grze(zwyciezca), reszta dała fold
            displayWinner();
            System.out.println("stake:" +stake);
            givePlayerTheirPrize();
            for(int i=1; i<players.length; i++){
            	System.out.println(players[i].getName()+ " ->" + players[i].getPlayersCoins());
            }
            prepareNewDeal();
			System.out.println("Koniec pierwszego rozdania");
		}
	}

	public void updateAllin(int playerNr){
		players[playerNr].setStatus(Player.PLAYER_PLAYED_ALLIN);
		String s = Integer.toString(players[playerNr].getPlayersCoins());
		playersInGameCounter--;
		try {
			updateBet(playerNr,s);
		}
		catch(GameException e){}
		displayNotes(players[playerNr].getName(), Player.PLAYER_PLAYED_ALLIN);
		
	}
	public void updateFold(int playerNr, boolean isPermament){
		if(isPermament) 
			players[playerNr].setStatus(Player.PLAYER_PERMAMENTLY_OUT_OF_GAME);
		else	
			players[playerNr].setStatus(Player.PLAYER_OUT_OF_GAME);
		System.out.println(" updateFold przed display");
		displayNotes(players[playerNr].getName(), Player.PLAYER_OUT_OF_GAME);
		playersInGameCounter--;
		playersFoldedCounter++;
		if (whoseTurn==playerNr)nextPlayerMove();
		else {
			System.out.println("------------" + players[playerNr].getName() + "dał fold nie w swojej turze---------");
			playersBeingInGameInActualRound--;
			if(playersBeingInGameInActualRound<2)
				nextPlayerMove();
		}
		
	}
	/*
	 * Method enable  pressing the buttons appropriately to the player state
	 * H-Check,B-Bet,R-Raise,C-Call,F-Fold,A-Allin,E-Exchange
	 */
	public void enableButtons(int PlayerNr, boolean isBet, boolean addCheck){
		int bet = players[PlayerNr].getBet();
		int coins = players[PlayerNr].getPlayersCoins();
		possibleMoves = Croupier.possibleMoves(bet, coins, isBet, maxBet, round);
		if(addCheck) possibleMoves= "H" + possibleMoves;
		playersComunicators[PlayerNr].enableButtons(possibleMoves);
	}
	private void enableCardsExchange(int PlayerNr){
		possibleMoves = "E";
		playersComunicators[PlayerNr].enableButtons("E");
	}
	public void exchangeCards(int PlayerNr, String binaryCardSet)throws GameException{
		try {
			int cardSet = Integer.parseInt(binaryCardSet, 2);
			if (cardSet>15 || cardSet<0) throw new GameException("You tried exchange more cards than you had had");
			if(cardSet>0){
				int exchangedCardsCounter = 0;
				for(int i=0; i<Hand.HAND_SIZE; i++){
					if(binaryCardSet.charAt(i)=='1')
						exchangedCardsCounter++;
				}
				players[PlayerNr].exchangeCards(deck, cardSet);
				playersComunicators[PlayerNr].displayCards(players[PlayerNr].getStringHand());
				for(int i = 1; i<players.length; i++){
					if (i!=PlayerNr) playersComunicators[i].displayAmountOfExchanged(players[PlayerNr].getName(),Integer.toString(exchangedCardsCounter));
				}
			}
			nextPlayerMove();
		}
		catch(NumberFormatException e) {
			throw new GameException("You send wrong cardSet");
		}
	}
	public void updateRound(){
		round++;
		moveCounter = 0;
		isBet = false;
		playersExchangedCardsCounter = 0;
		playersBeingInGameInActualRound = playersInGameCounter;
		whoseTurn = playerWithSmallBlind;
		for(int i=1; i<players.length; i++){
			playersComunicators[i].displayRound(Integer.toString(round));
		}
	}
	public void prepareNewDeal() {
		int playersPermanentlyFoldedCounter = 0;
		for(int i=1; i<players.length; i++){//chyba trzeba zwrócić karty gracza odłaczonego
			players[i].getPlayersHand().discardAllCards(deck);
			players[i].setBet(0);
			if(players[i].getStatus()!=Player.PLAYER_PERMAMENTLY_OUT_OF_GAME && players[i].getPlayersCoins()>0){
				players[i].setStatus(Player.PLAYER_IN_GAME);
			}
			else {
				System.out.println("SERVER daje permamentnego folda "+ players[i].getName());
				players[i].setStatus(Player.PLAYER_PERMAMENTLY_OUT_OF_GAME);
                players[i].setBet(0);
				playersPermanentlyFoldedCounter++;
				deletePlayer(i);
			}
		}
		System.out.println("shuffle");
		deck.shuffle();
		
		if( (plNr-playersPermanentlyFoldedCounter)<2){
			int i = 1;
			while(players[i].getStatus()!=Player.PLAYER_IN_GAME) {
				i++;
			}
			for(int j = 1; j<players.length; j++) {
				if (j==i) playersComunicators[j].displayInfo("You won.\nThe game is over",true);
				else playersComunicators[j].displayInfo(players[i].getName().trim() + " won.\nThe game is over",true);
			}
		}
		else{
			System.out.println("po shuffle");
			for(int i=1; i<players.length; i++){
				if(players[i].getStatus()!=Player.PLAYER_PERMAMENTLY_OUT_OF_GAME){
					players[i].getPlayersHand().drawAllCards(deck, i);
					for(int j=1; j<players.length; j++){
						if(playersComunicators[j].isWorking())playersComunicators[j].displayBet("0", players[i].getName());
					}
				}
			}
			round = 1;
			stake = 0;
			isBet = false;
			playersFoldedCounter = playersPermanentlyFoldedCounter;
			playersInGameCounter = plNr-playersPermanentlyFoldedCounter;
			playersExchangedCardsCounter = 0;
			playersBeingInGameInActualRound = playersInGameCounter;
			try
			{
				whoDealer = Croupier.whichPlayerNext2(whoDealer, players, false);
			} catch (GameException e){}
			for(int i=1; i<players.length; i++){
				playersComunicators[i].displayRound(Integer.toString(round));
			}
		}
	}
	public void startNewDeal(){
		try
		{
			setPlayersWithBlinds();
			playersBeingInGameInActualRound = playersInGameCounter;
			whoseTurn = Croupier.whichPlayerNext2(whoseTurn, players,false);
			for(int i=1; i<players.length; i++)
				if(playersComunicators[i].isWorking() &&players[i].getStatus()!=Player.PLAYER_PERMAMENTLY_OUT_OF_GAME){
					playersComunicators[i].displayCards(players[i].getStringHand());
				}
			moveCounter = 1;
			enableButtons(whoseTurn, isBet, false);
		} catch (GameException e)
		{
			int i = 1;
			while(players[i].getStatus()!=Player.PLAYER_IN_GAME) {
				i++;
			}
			for(int j = 1; j<players.length; j++) {
				if (j==i) playersComunicators[j].displayInfo("You won.\nThe game is over",true);
				else playersComunicators[j].displayInfo(players[i].getName().trim() + " won.\nThe game is over",true);
			}
			//System.exit(1);
		}	
	}
	public void deletePlayer(int number){
		for(int i=1; i<players.length; i++){
			if(i!=number){
				playersComunicators[i].deletePlayer(players[number].getName());
			}
		}
	}
	public boolean isNameFree(String name){
		for(int i=1; i<=sitingPlayersCounter; i++){
				if(name.equals(players[i].getName()))	return false;
		}
		return true;
	}
	public void closeResults(int playerNr){
		if (players[playerNr].getStatus()!=Player.PLAYER_PERMAMENTLY_OUT_OF_GAME) playersClosedResults++;
		if (playersClosedResults == playersInGameCounter){
			startNewDeal();
			playersClosedResults = 0;
		}
	}
	public boolean isThisPlayerTurn(int playerNr) {
		if(whoseTurn == playerNr) return true;
		else return false;
	}
	public boolean isThisMoveAllowed(int playerNr, CharSequence s) {
		if(possibleMoves.contains(s)) return true;
		else {
			playersComunicators[playerNr].displayInfo("You are folded for cheating",false);
			updateFold(playerNr,true);
			return false;
		}
	}
	public void end(int playerNr) {
		int j = 0;
		for(int i=1; i<players.length; i++) {
			if(playersComunicators[i].isWorking()) j++;
			System.out.println(j);
		}
		if(j == plNr) System.exit(1);
	}
	
}
